//
// Created by martin on 7/7/21.
//

/* crypt / crypt_r函数需要 */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE  1 /* 需要gnu99支持 */
#endif

#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <pwd.h>
#include <shadow.h>
#include <stdlib.h>
#include <crypt.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <bits/posix1_lim.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <dirent.h>
#include <sys/sendfile.h>
#include <time.h>
#include <error.h>
#include <netinet/tcp.h>
#include <grp.h>

#include "ftpcmdaction.h"
#include "common.h"
#include "strex.h"
#include "session.h"
#include "utils.h"
#include "config.h"


static void action_user(session_t *pses);
static void action_pass(session_t *pses);
static void action_cwd(session_t *pses);
static void action_quit(session_t *pses);

static void action_port(session_t *pses);
static void action_pasv(session_t *pses);
static void action_type(session_t *pses);

static void action_size(session_t *pses);
static void action_retr(session_t *pses);
static void action_rest(session_t *pses);
static void action_abor(session_t *pses);
static void action_pwd(session_t *pses);
static void action_list(session_t *pses);
static void action_syst(session_t *pses);
static void action_help(session_t *pses);
static void action_noop(session_t *pses);


/**
 * 用户请求命令-响应动作映射表
 * @note 列出了RFC 959规定的所有命令，不支持的命令，对应action为NULL
 */
const ftpcmdaction_t cmd_action[] = {
        /* 访问控制命令 */
        {"USER", action_user},
        {"PASS", action_pass},
        {"ACCT", NULL},
        {"CWD",  action_cwd},
        {"CDUP", NULL},
        {"SMNT", NULL},
        {"REIN", NULL},
        {"QUIT", action_quit},

        /* 传输参数命令 */
        {"PORT", action_port},
        {"PASV", action_pasv},
        {"TYPE", action_type},
        {"STRU", NULL},
        {"MODE", NULL},

        /* FTP服务命令 */
        {"SIZE", action_size},
        {"RETR", action_retr},
        {"STOR", NULL},
        {"STOU", NULL},
        {"APPE", NULL},
        {"ALLO", NULL},
        {"REST", action_rest},
        {"RNFR", NULL},
        {"RNTO", NULL},
        {"ABOR", action_abor},
        {"DELE", NULL},
        {"USER", NULL},
        {"RMD", NULL},
        {"MKD", NULL},
        {"PWD",  action_pwd},
        {"LIST", action_list},
        {"NLST", NULL},
        {"SITE", NULL},
        {"SYST", action_syst},
        {"STAT", NULL},
        {"HELP", action_help},
        {"NOOP", action_noop},
};

int getcmdactionsize()
{
    return sizeof(cmd_action) / sizeof(cmd_action[0]);
}

/**
 * 打开数据连接，尝试在指定端口返回，监听一个有效端口号
 */
int ftp_create_datasocket(const char *ip, int start_port, int end_port)
{
    int listenfd = -1;
    int i;
    if (start_port < 1024) start_port = 1024;
    if (end_port >= 65535) end_port = 65535;
    if (start_port > end_port) return -1;

    for (i = start_port; i <= end_port; ++i) {
        if ((listenfd = socket_create(ip, i)) != -1) /* success */
            break;
    }

    return listenfd;
}

/**
 * 进入PASV mode，如果没有创建监听套接字，则新建一个
 */
static int enter_passive_mode(session_t *pses)
{
    int listenfd = -2;
    pses->dataconn_mode = DataConn_Mode_PASV;

    if (pses->dataconn_listenfd <= 0) { /* 没有创建过监听套接字 */
        listenfd = ftp_create_datasocket(pses->ip, DEFAULT_DATA_PORT_MIN, DEFAULT_DATA_PORT_MAX);
        if (listenfd < 0) { /* error 或 指定端口范围全部被占用 */
            return -1;
        }
        pses->dataconn_listenfd = listenfd; /* 记录本次会话数据连接fd */

        struct sockaddr_in servaddr;
        socklen_t len = sizeof(servaddr);
        getsockname(listenfd, &servaddr, &len); /* socket fd => local ip & port */

        inet_ntop(AF_INET, &servaddr.sin_addr, pses->ip, sizeof(pses->ip));
        pses->data_port = ntohs(servaddr.sin_port); /* 记录数据连接端口号 */

        return listenfd;
    }
    else {
        return pses->dataconn_listenfd;
    }
}

static int exit_passive_mode(session_t *pses)
{
    /* 关闭数据连接 */
    session_close_dataconn(pses);

    /* 释放数据连接监听套接字 */
    if (pses->dataconn_listenfd > 0) {
        close(pses->dataconn_listenfd);
        pses->dataconn_listenfd = 0;
    }

    return 0;
}

/**
 * USER 命令响应动作
 * @note 检查用户名, 并反馈给客户端相应结果
 */
static void action_user(session_t *pses)
{
    struct passwd *pw;
    if (pses == NULL) return;

    /* 验证用户名
     * 通过用户名查找本地密码数据库(/etc/passwd). 非线程安全
     * */
    pw = getpwnam(pses->arg);
    if (pw == NULL) { /* 没有找到密码项, 说明用户不存在 */
        send_reply(pses->ctrlconn_sockfd, 530, "Loggin incorrect"); /* 530: Loggin incorrect  */
        return;
    }

    /* 找到用户对应密码项 */
    pses->uid = pw->pw_uid;
    send_reply(pses->ctrlconn_sockfd, 331, "User name okay, need password. ");           /* 331: 用户名OK, 需要密码 */
}

/**
 * 检查登入密码, 并反馈给客户端相应结果
 */
static void action_pass(session_t *pses)
{
    struct passwd *pw;
    if (pses == NULL) return;

    /* 验证密码前， 先验证用户名
     * 通过用户名查找本地密码数据库(/etc/passwd). 非线程安全 */
    pw = getpwuid(pses->uid); /* 根据当前会话uid, 取得密码项结构passwd */
    if (pw == NULL) {
        send_reply(pses->ctrlconn_sockfd, 332, "Need account for login.");  /* 332: 需要帐户才能登录 */
        return;
    }

    struct spwd sbuf = {0};
    struct spwd *spbufp = NULL;
    char buf[1000] = {0};

    if (getRootPrivileged() < 0) {
        send_reply(pses->ctrlconn_sockfd, 530, "Loggin incorrect"); /* 530: Loggin incorrect  */
        return;
    }

    /* 读取影子文件(/etc/shadow), 获取shadow password file entry. 需要root权限 */
    if (getspnam_r(pw->pw_name, &sbuf, buf, sizeof(buf), &spbufp) < 0) {
        perror("getspnam_r error 1");
        send_reply(pses->ctrlconn_sockfd, 530, "Loggin incorrect"); /* 530: Loggin incorrect  */
        return;
    }
    if (spbufp == NULL) {
        perror("getspnam_r error 2");
        send_reply(pses->ctrlconn_sockfd, 530, "Loggin incorrect"); /* 530: Loggin incorrect  */
        return;
    }

    struct crypt_data data;
    char *encrypted_pass;
    /*
     * 根据用户输入的明文密码，加密得到 加密密码
     * 需要链接 -lcrypt 库. 非线程安全 */
    encrypted_pass = crypt_r(pses->arg, spbufp->sp_pwdp, &data);
    if (encrypted_pass == NULL) { /* error */
        perror("crypt error");
        send_reply(pses->ctrlconn_sockfd, 530, "Loggin incorrect"); /* 530: Loggin incorrect  */
        return;
    }
    if (strcmp(encrypted_pass, spbufp->sp_pwdp) != 0) { /* 密码不匹配 */
        send_reply(pses->ctrlconn_sockfd, 530, "Loggin incorrect"); /* 530: Loggin incorrect  */
    }

    send_reply(pses->ctrlconn_sockfd, 230, "Login successful"); /* 230: 登录成功 */

    /* 将当前用户切换至用户请求的登入用户:
     * 1.切换用户组 gid
     * 2.切换用户 uid
     */
    if (setegid(pw->pw_gid) < 0) {
        perror("setgid error");
        session_close(pses);
    }
    if (seteuid(pw->pw_uid) < 0) {
        perror("setuid error");
        session_close(pses);
    }

    /* 当前工作目录切换至主目录 */
    if (chdir(pw->pw_dir) < 0) {
        perror("chdir error");
        session_close(pses);
    }

    if (NULL == getcwd(pses->cur_work_dir, sizeof(pses->cur_work_dir))) { /* 记录当前工作目录 */
        perror("action_cwd getcwd error");
    }
}

#include <error.h>
/**
 * 改变工作目录
 * @note
 * 接收命令格式：CWD<SP><working directory><CRLF>
 */
static void action_cwd(session_t *pses)
{
    struct stat st;
    char file_path[_POSIX_PATH_MAX];

    if (pses == NULL) return;
    if (session_checkloginstat(pses) < 0) {
        send_reply(pses->ctrlconn_sockfd, 530, "Loggin incorrect"); /* 530: 没有登录 */
    }

    if (strlen(pses->arg) == 0) { /* 参数为空，切换到默认目录 */
        if (NULL == getcwd(file_path, sizeof(file_path))) { /* 当前工作目录 */
            perror("action_cwd getcwd error");
        }
    }
    else if (pses->arg[0] != '/') { /* 要切换的目录不以"/"开头, 表明是相对路径 */
        char dir[_POSIX_PATH_MAX];
        if (NULL == getcwd(dir, sizeof(dir))) { /* 当前工作目录 */
            perror("action_cwd getcwd error");
        }
        snprintf(file_path, sizeof(file_path), "%s/%s", dir, pses->arg);
        pses->cmd_cwd_path = CMD_CWD_PATH_TYPE_RELATIVE;
    }
    else { /* 其他情况当作绝对路径 */
        strcpy(file_path, pses->arg);
        pses->cmd_cwd_path = CMD_CWD_PATH_TYPE_ABSULOTE;
    }

    if (stat(file_path, &st) < 0) { /* 读取文件状态出错 */
        perror("action_cwd stat error");
        send_reply(pses->ctrlconn_sockfd, 550, "No such file or directory."); /* 550: 请求的动作没有执行 */
        return;
    }

    if (S_ISDIR(st.st_mode)) { /* 要切换的参数是目录 */
        if (chdir(file_path) < 0) {
            fprintf(stderr, "chdir error on filepath %s: %s\n", file_path, strerror(errno));
            send_reply(pses->ctrlconn_sockfd, 550, "No such file or directory."); /* 550: 请求的动作没有执行 */
            return;
        }

        pr_currentdir_r();
        send_reply(pses->ctrlconn_sockfd, 250, "CMD command successful"); /* 250: 请求文件动作OK，完成; 200也可以 */
    } else {/* 要切换的参数不是目录 */
        send_reply(pses->ctrlconn_sockfd, 550, "No such file or directory."); /* 550: 请求的动作没有执行 */
    }
}

static void action_quit(session_t *pses)
{
    if (pses == NULL) return;

    exit_passive_mode(pses);
    session_close(pses);
    send_reply(pses->ctrlconn_sockfd, 221, "Goodbye."); /* 221: 服务关闭控制连接 */
}

/**
 * 请求服务器连接指定host ip & port(通常是用户主机或第三方主机)
 * @note 当用户想通过当前主机从第三方服务器获取文件时， 使用PORT命令
 * PORT命令请求格式: PORT h1,h2,h3,h4,p1,p2
 * 其中, ip地址: h1.h2.h3.h4 ; 16bit端口号: (p1 << 8) + p2
 */
static void action_port(session_t *pses)
{
    unsigned int h[4] = {0}; /* 存储从命令参数提取的ip地址分段 */
    unsigned int p[2] = {0}; /* 存储从命令参数提取的端口号分段 */
    unsigned int port, i;
    int valid_ip = 1;

    if (NULL == pses) return;

    /* 检查是否登录 */
    if (session_checkloginstat(pses) < 0) {
        send_reply(pses->ctrlconn_sockfd, 530, "Loggin incorrect"); /* 530: 没有登录 */
        return;
    }

    sscanf(pses->arg, "%u,%u,%u,%u,%u,%u", &h[0], &h[1], &h[2], &h[3], &p[0], &p[1]);

    /* 验证ip地址及端口号是否合法 */
    for (i = 0; i < sizeof(h) / sizeof(h[0]); ++i) {
        if (h[i] > 255) { /* ip地址非法 */
            valid_ip = 0;
            break;
        }
    }
    port = (h[0] << 8) + h[1];
    if (port < 1023 || port > 65535) {
        valid_ip = 0;
    }

    if (0 != valid_ip) {
        send_reply(pses->ctrlconn_sockfd, 501, "host port incorrect"); /* 501: 参数不合法 */
    }
    else {
        pses->dataconn_mode = DataConn_Mode_PORT;
        send_reply(pses->ctrlconn_sockfd, 200, "Command okay.");  /* 200: 命令OK */
    }
}

/**
 * 阻塞等待建立数据连接
 */
static int open_data_conn(session_t *pses)
{
    if (NULL == pses) return -1;
    int connfd;

    if ((connfd = socket_accept(pses->dataconn_listenfd)) < 0) {
        perror("socket_accpet error for open_data_conn");
        return -1;
    }

    pses->dataconn_sockfd = connfd;
    return connfd;
}


/**
 * PASV 命令指定服务器数据连接监听端口, 而不是主动发起连接
 * @note
 * 回复227, 进入被动模式的的格式为:
 * 形如"Entering Passive Mode (192,168,0,108,132,133)."
 * 括号里面(h1,h2,h3,h4,p1,p2), 与ipv4和端口port关系:
 * ipv4 = "h1.h2.h3.h4"
 * port = (p1 << 8) + p2
 *
 * 其他建议格式: 227 =h1,h2,h3,h4,p1,p2
 */
static void action_pasv(session_t *pses)
{
    char buf[100];
    uint8_t h[4];
    int p[2];

    if (NULL == pses) return;

    /* 检查是否登录 */
    if (session_checkloginstat(pses) < 0) {
        send_reply(pses->ctrlconn_sockfd, 530, "Loggin incorrect"); /* 530: 没有登录 */
        return;
    }

    /* 从控制连接获取本地IP地址, 作为数据连接的IP地址 */
    struct sockaddr_in sockaddr;
    socklen_t len = sizeof(sockaddr);
    getsockname(pses->ctrlconn_sockfd, &sockaddr, &len);  /* 获取与已连接sockfd关联的本地协议地址 */

    /* 将ip地址和端口号转化为PASV响应文本 */
    h[3] = (sockaddr.sin_addr.s_addr >> 24) & 0xFF;
    h[2] = (sockaddr.sin_addr.s_addr >> 16) & 0xFF;
    h[1] = (sockaddr.sin_addr.s_addr >> 8) & 0xFF;
    h[0] = (sockaddr.sin_addr.s_addr >> 0) & 0xFF;

    /* 数据连接进入PASV模式 */
    inet_ntop(AF_INET, &sockaddr.sin_addr, pses->ip, sizeof(pses->ip));
    if (enter_passive_mode(pses) < 0) { /* error */
        perror("enter_passive_mode error");
        send_reply(pses->ctrlconn_sockfd, 421, "Service not available, closing control connection.");  /* 421: 服务不可用关闭控制连接 */
        return;
    }
    /* 数据连接端口号 */
    p[0] = (pses->data_port >> 8) & 0xFF;
    p[1] = pses->data_port & 0xFF;

    snprintf(buf, sizeof(buf), "Entering Passive Mode (%u,%u,%u,%u,%d,%d).", h[0],h[1],h[2],h[3],p[0],p[1]);
    send_reply(pses->ctrlconn_sockfd, 227, buf); /* 227: 进入被动模式 */

    /* 阻塞等待建立FTP数据连接 */
    if (open_data_conn(pses) < 0) {
        send_reply(pses->ctrlconn_sockfd, 421, "Service not available, closing control connection.");  /* 421: 服务不可用关闭控制连接 */
        return;
    }
}

/**
 * TYPE命令参数指定在数据表示和存储部分介绍的表示类型
 * @note
 * TYPE命令第1个参数:
 * "TYPE A" - type ASCII (默认)
 * "TYPE E" - type EBCDIC
 * "TYPE I" - type Image
 * "TYPE L" - type local
 *
 * ASCII和EBCDIC类型也支持第二个可选的参数. 这代表了一种纵向的文件格式控制. 第2个参数:
 * NON PRINT(非打印) - 缺省
 * TELNET 格式控制
 * CARRIAGE CONTROL(ASA)
 *
 * 本函数type只支持type ASCII, type Image
 * 格式控制只支持默认缺省的NON PRINT
 */
static void action_type(session_t *pses)
{
    if (pses == NULL) return;

    /* 检查是否登录 */
    if (session_checkloginstat(pses) < 0) {
        send_reply(pses->ctrlconn_sockfd, 530, "Loggin incorrect"); /* 530: 没有登录 */
        return;
    }

    /* 切换文件传输模式: ASICC, Binary */
    if (strcmp(pses->arg, "A") == 0) {
        pses->transfer_type = Transfer_Type_ASCII;
        send_reply(pses->ctrlconn_sockfd, 200, "Switching to ASCII mode");  /* 200: 命令OK */
    }
    else if (strcmp(pses->arg, "I") == 0){
        pses->transfer_type = Transfer_Type_Binary;
        send_reply(pses->ctrlconn_sockfd, 200, "Swiching to Binary mode");  /* 200: 命令OK */
    }
    else if ((strcmp(pses->arg, "E") == 0) || (strcmp(pses->arg, "L") == 0)){
        pses->transfer_type = Transfer_Type_ASCII;
        send_reply(pses->ctrlconn_sockfd, 504, "Unimplemented parameter for the command"); /* 504: 没有实现这个命令参数 */
    }
    else {
        pses->transfer_type = Transfer_Type_ASCII;
        send_reply(pses->ctrlconn_sockfd, 501, "Incorrect parameter"); /* 501: 参数语法错误 */
    }
}

/**
 * 获取文件大小
 * @note 普通文件，并且当前用户具有权限，才回200
 */
static void action_size(session_t *pses)
{
    char buf[100];

    if (pses == NULL) return;

    /* 检查是否登录 */
    if (session_checkloginstat(pses) < 0) {
        send_reply(pses->ctrlconn_sockfd, 530, "Loggin incorrect"); /* 530: 没有登录 */
        return;
    }

    /* 文件路径名长度检查 */
    if (strlen(pses->arg) + 1> PATH_MAX) { /* 文件路径名超过最大限制 */
        send_reply(pses->ctrlconn_sockfd, 501, "Get size error: File path is too long."); /* 500：参数语法错误 */
        return;
    }

    /* 获取文件状态 */
    struct stat st;
    if (stat(pses->arg, &st) < 0) { /* 读取文件状态出错 */
        fprintf(stderr, "action_size stat error on file %s: %s\n", pses->arg, strerror(errno));
        send_reply(pses->ctrlconn_sockfd, 550, "Access is denied."); /* 550： 文件不可用*/
        return;
    }

    if (!S_ISREG(st.st_mode)) { /* 非普通文件 */
        send_reply(pses->ctrlconn_sockfd, 550, "Access is denied."); /* 550： 文件不可用*/
        return;
    }

    snprintf(buf, sizeof(buf), "%ld", st.st_size); /* 文件长度 */
    send_reply(pses->ctrlconn_sockfd, 213, buf); /* 213: 文件状态 */
}

/**
 * 用户获取指定文件
 */
static void action_retr(session_t *pses)
{
    int fd;
    int errflg = 0;
    struct stat st;

    if (pses == NULL) return;

    /* 检查是否登录 */
    if (session_checkloginstat(pses) < 0) {
        send_reply(pses->ctrlconn_sockfd, 530, "Loggin incorrect"); /* 530: 没有登录 */
        return;
    }

    /* 检查数据连接是否打开 */
    if (pses->dataconn_sockfd < 0) {
        send_reply(pses->ctrlconn_sockfd, 425, "Can't open data connection."); /* 425: 不能打开数据连接 */
        return;
    }

    if ((fd = open(pses->arg, O_RDONLY)) < 0) { /* 文件打开异常 */
        fprintf(stderr, "action_retr open error on file %s: %s\n", pses->arg, strerror(errno));
        send_reply(pses->ctrlconn_sockfd, 550, "Access is denied."); /* 550: 请求的动作没有执行 */
        return;
    }
    if (fstat(fd, &st) < 0) {/* 获取文件状态异常 */
        perror("action_retr fstat error");
        close(fd);
        send_reply(pses->ctrlconn_sockfd, 550, "File stat error."); /* 550: 请求的动作没有执行 */
        return;
    }

    /* 数据连接OK + 文件状态OK */
    send_reply(pses->ctrlconn_sockfd, 125, "Data connection already open; Transfer starting."); /* 125: 已打开数据连接, 准备传输数据 */

#if 0 /* 启动TCP_NODELAY <=> 禁用Nagle算法 <=> 允许小数据包发送 */
    char flag;
    getsockopt(pses->dataconn_sockfd,IPPROTO_TCP, TCP_NODELAY,&flag, sizeof(flag));
    printf("TCP_NODELAY flag1 = %d\n", flag);

    flag = 1;
    setsockopt(pses->dataconn_sockfd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));

    getsockopt(pses->dataconn_sockfd,IPPROTO_TCP, TCP_NODELAY,&flag, sizeof(flag));
    printf("TCP_NODELAY flag2 = %d\n", flag);
#endif

#if 0 /* TCP发送超时定时器 */
    struct timeval tv;
    tv.tv_sec = 30;
    setsockopt(pses->dataconn_sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); /* 设置发送超时时间 */
#endif

    pses->dataconn_close = 0;

    off_t offset = pses->cur_start_pos; /* 32bit, 最大值 INT32_MAX */
    pses->cur_start_pos = 0; /* 清除断点续传位置标记 */
    long int nleft = st.st_size;
    ssize_t nbytes;
    char buf[4096];
    while (nleft > 0) {
        if (pses->dataconn_close == 1) break;

//        nbytes = sendfile64(pses->dataconn_sockfd, fd, &offset, nleft); /* 将包含指定目录下文件信息的文件，通过数据连接发送给客户端 */
        if (lseek(fd, offset, SEEK_SET) < 0) { /* 重定位文件fd offset错误 */
            perror("lseek error");
            break;
        }
        int nread = read(fd, buf, sizeof(buf)); /* 读取文件fd内容到缓冲区 */
        if (nread < 0) {
            perror("read error");
            exit(1);
        }

        nbytes = send(pses->dataconn_sockfd, buf, sizeof(buf), MSG_NOSIGNAL); /* avoid signal EPIPIE */
        if (nbytes == -1) {
            if (errno == EINTR) continue; /* recover send from interrupted */
            if (errno == EAGAIN || errno == EWOULDBLOCK)
                perror("action_retr send file timeout");
            else
                perror("action_retr send file error");
            errflg = 1;
            break;
        }
        else if (nbytes == 0) {
            printf("peer shutdown\n");
            break;
        }

        nleft -= nbytes;
        offset += nbytes;
    }

    close(fd); /* 关闭发送文件 */
    shutdown(pses->dataconn_sockfd, SHUT_WR);  /* 关闭数据连接 */

    if (0 == errflg) { /* no error */
        send_reply(pses->ctrlconn_sockfd, 226, "Transfer complete."); /* 226: 传输完成，关闭数据连接 */
    }
    else { /* error */
        send_reply(pses->ctrlconn_sockfd, 451, "Requested action aborted."); /* 451: 本地错误 */
    }
}

/**
 * 断点续传
 * REST startpos
 * startpos 是非负数
 */
static void action_rest(session_t *pses)
{
    char text[100];

    /* 检查是否登录 */
    if (session_checkloginstat(pses) < 0) {
        send_reply(pses->ctrlconn_sockfd, 530, "Loggin incorrect"); /* 530: 没有登录 */
        return;
    }

    sscanf(pses->arg, "%d", &pses->cur_start_pos); /* string => int(dec) */
    if (pses->cur_start_pos < 0) { /* 起始位置负数 */
        pses->cur_start_pos = 0;
        send_reply(pses->ctrlconn_sockfd, 501, "Invalid argument"); /* 501: 参数语法错误 */
        return;
    }

    pses->dataconn_close = 1;
    snprintf(text, sizeof(text), "Restarting at %d. Send STORE or RETRIEVE to initiate transfer.", pses->cur_start_pos);
    send_reply(pses->ctrlconn_sockfd, 350, text); /* 350: 没有登录 */
}

/**
 * 放弃先前FTP命令，关闭数据连接
 * @param pses
 */
static void action_abor(session_t *pses)
{
    if (pses->dataconn_listenfd > 0) {
        pses->dataconn_close = 1;
        send_reply(pses->ctrlconn_sockfd, 226, "ABOR command successful."); /* 226: 关闭数据连接 */
    }
}

/**
 * 获取当前工作目录
 */
static void action_pwd(session_t *pses)
{
    char buf[_POSIX_PATH_MAX];
    if (pses == NULL) return;

    /* 检查是否登录 */
    if (session_checkloginstat(pses) < 0) {
        send_reply(pses->ctrlconn_sockfd, 530, "Loggin incorrect"); /* 530: 没有登录 */
        return;
    }

    /* 读取当前工作目录 */
    if (getcwd(buf, sizeof(buf)) == NULL) {
        perror("getcwd error");
        send_reply(pses->ctrlconn_sockfd, 550, "Requested action not taken."); /* 550: 请求的动作没有执行 */
        return;
    }

    if (pses->cmd_cwd_path == CMD_CWD_PATH_TYPE_RELATIVE) { /* 相对路径 */
        char *pos = strrchr(buf, '/');
        char *p = pos + 1;
        int i = 0;
        while (*p != '\0') {
            buf[i++] = *p;
            p++;
        }
        buf[i] = '\0';
        send_reply(pses->ctrlconn_sockfd, 257, buf); /* 257: 打印工作目录 */
    }
    else { /* 绝对路径 */
        send_reply(pses->ctrlconn_sockfd, 257, buf); /* 257: 打印工作目录 */
    }
}

static int get_permission_10bit(char *filename, char *buf, size_t len)
{
    char perm[] = "----------"; /* "-rwxrwxrwx" */
    struct stat st;

    if (buf == NULL || len < sizeof(perm)) return -1;

    if (stat(filename, &st) < 0) {
        perror("get_permission_10bit stat error");
        return -1;
    }

    /* 文件类型 */
    switch (st.st_mode & S_IFMT) {
        case S_IFREG: /* 普通文件 */
            perm[0] = '-';
            break;

        case S_IFDIR:/* 目录 */
            perm[0] = 'd';
            break;

        case S_IFLNK: /* 符号链接文件(软链接) */
            perm[0] = 'l';
            break;

        case S_IFCHR: /* 字符设备 */
            perm[0] = 'c';
            break;

        case S_IFBLK: /* 块设备 */
            perm[0] = 'b';
            break;

        default: /* 不支持文件类型, 都当作普通文件 */
            perm[0] = '-';
    }

    /* 文件拥有者权限 */
    if (st.st_mode & S_IRUSR) {
        perm[1] = 'r';
    }
    if (st.st_mode & S_IWUSR) {
        perm[2] = 'w';
    }
    if (st.st_mode & S_IXUSR) {
        perm[3] = 'x';
    }

    /* 同组用户权限 */
    if (st.st_mode & S_IRGRP) {
        perm[4] = 'r';
    }
    if (st.st_mode & S_IWGRP) {
        perm[5] = 'w';
    }
    if (st.st_mode & S_IXGRP) {
        perm[6] = 'x';
    }

    /* 其他组用户权限 */
    if (st.st_mode & S_IROTH) {
        perm[7] = 'r';
    }
    if (st.st_mode & S_IWOTH) {
        perm[8] = 'w';
    }
    if (st.st_mode & S_IXOTH) {
        perm[9] = 'x';
    }

    strncpy(buf, perm, sizeof(perm));
    return 0;
}



/**
 * /bin/ls格式：
 * 形如"$ ls -l"输出结果的格式
 * drwxr-xr-x  2 martin martin  4096 Jul 23 11:41 Music
 * -rw-r--r--  1 martin martin  32 Jun 20 11:47 test.txt
 * 文件权限(-rw-r--r--) 链接数 拥有者 组 文件大小 最后修改日期 文件名
 *
 * 参考: https://cr.yp.to/ftp/list/binls.html
 */
int list_file_lsformat(char *basedir, char *filename)
{
    DIR *dirp = NULL;
    struct dirent *dp;
    int fd;
    char item[PATH_MAX];
    char tmpfile[20] = "list.XXXXXX";
    struct stat st;
    int line_num = 0;

    /* open dir basefir */
    dirp = opendir(basedir);
    if (NULL == dirp) {
        perror("opendir error");
        return -1;
    }

    /* create temp file recording items */
    if ((fd = mkstemp(tmpfile)) < 0) {
        perror("mkstemp error");
        return -1;
    }

    while ((dp = readdir(dirp)) != NULL) { /* 遍历目录下所有文件 */
        /* 排除当前目录".", 上一级目录".." */
        if (0 == strcmp(dp->d_name, ".") || 0 == strcmp(dp->d_name, ".."))
            continue;

        int off = 0;

        /* 10bit 权限位 */
        get_permission_10bit(dp->d_name, item, sizeof(item));
        off += strlen(item);

        stat(dp->d_name, &st);
        /* 链接数 */
        off += snprintf(item + off, sizeof(item) - off, " %lu", st.st_nlink);

        /* 拥有者 */
        struct passwd *pwd;
        pwd = getpwuid(st.st_uid);
        if (pwd == NULL) {
            perror("getpwuid error");
            return -1;
        }
        off += snprintf(item + off, sizeof(item) - off, " %s", pwd->pw_name);

        /* 所属组 */
        struct group *pgp;
        pgp = getgrgid(st.st_gid);
        if (pgp == NULL) {
            perror("getgrgid error");
            return -1;
        }
        off += snprintf(item + off, sizeof(item) - off, " %s", pgp->gr_name);

        /* 文件大小 */
        off += snprintf(item + off, sizeof(item) - off, " %ld", st.st_size);

        /* 最后修改时间 */
        struct tm *tmp;
        char date[50];
        time_t now = time(NULL);

        tmp = localtime(&st.st_mtime);

        if (now < st.st_mtime + (365 / 2) * 24 * 3600) { /* 半年以内 */
            strftime(date, sizeof(date), " %b %d %R", tmp);
        }
        else { /* 半年以外 */
            strftime(date, sizeof(date), " %b %d %Y", tmp);
        }
        off += snprintf(item + off, sizeof(item) - off, " %s", date);

        /* 文件名 */
        if (S_ISLNK(st.st_mode)) { /* 链接文件 */
            char link_file[256];
            if (readlink(dp->d_name, link_file, sizeof(link_file)) < 0) { /* 读取软链接指向的文件 */
                perror("readlink error");
                return -1;
            }
            snprintf(item + off, sizeof(item) - off, " %s -> %s\r\n", dp->d_name, link_file);
        }
        else {
            snprintf(item + off, sizeof(item) - off, " %s\r\n", dp->d_name);
        }

        /* 将文件项写入文件fd */
        if (write(fd, item, strlen(item)) < 0) {
            perror("write error");
            return -1;
        }

        ++line_num;
    }
    closedir(dirp);
    close(fd);

    if (filename != NULL) {
        strcpy(filename, tmpfile);
    }

    return line_num;
}

/**
 * 列出指定目录basedir下面的所有文件名, 并写入临时文件, 返回文件名filename
 * @note
 * 1. 简单格式:
 * 目录：(最后修改)日期 时间 <DIR> 目录名
 * 普通文件：(最后修改)日期 时间 长度 文件名
 *
 * 例子：
 * 07-23-21 11:41AM   <DIR>  Music
 * 06-20-21 13:46PM  32 test.txt
 *
 * 2./bin/ls格式：
 * 形如"$ ls -l"输出结果的格式
 * drwxr-xr-x  2 martin martin  4096 Jul 23 11:41 Music
 * -rw-r--r--  1 martin martin  32 Jun 20 11:47 test.txt
 */
int list_file(char *basedir, char *filename)
{
    DIR *dirp = NULL;
    struct dirent *dp;
    int fd;
    int n;
    char item[PATH_MAX];
    char tmpfile[20] = "list.XXXXXX";
    struct stat st;
    struct tm *tmp;
    char type[40];

    /* open dir basefir */
    dirp = opendir(basedir);
    if (NULL == dirp) {
        perror("opendir error");
        return -1;
    }

    /* create temp file recording items */
    if ((fd = mkstemp(tmpfile)) < 0) {
        perror("mkstemp error");
        return -1;
    }

    while ((dp = readdir(dirp)) != NULL) {/* walk all files in the directory */
        /* 排除当前目录".", 上一级目录".." */
        if (0 == strcmp(dp->d_name, ".") || 0 == strcmp(dp->d_name, ".."))
            continue;

        stat(dp->d_name, &st);
        tmp = localtime(&st.st_mtime);

        if ((n = strftime(item, sizeof(item), "%m-%d-%y %R%p ", tmp)) == 0) {
            printf("buffer is too small");
            continue;
        }

        int type_len = 0;
        int filesize_len = 0;
        switch (dp->d_type) {
            case DT_DIR: /* 目录 */
                strcpy(type, "  <DIR>  ");
                strcat(item, type);
                type_len = strlen(type);
                break;
            case DT_REG: /* 普通文件 */
                filesize_len = snprintf(item + n, sizeof(item) - n,
                                        " %ld ",
                                        st.st_size);
                break;
            default:
                strcpy(type, "");
        }

        snprintf(item + n + type_len + filesize_len, sizeof(item) - n - type_len - filesize_len,
                 "%s\r\n",
                 dp->d_name);
        if ((n == write(fd, item, strlen(item))) < 0) {
            perror("write error");
            return -1;
        }
    }
    closedir(dirp);
    close(fd);

    if (filename != NULL) {
        strcpy(filename, tmpfile);
    }

    return 0;
}

/**
 * 列出目录
 * @note
 * LIST命令2种形式：
 * 1) 参数缺省： LIST<CRLF> 或者LIST -l<CRLF>
 * 2) 指定路径参数： LIST /<CRLF>
 */
static void action_list(session_t *pses)
{
    if (pses == NULL) return;
    char tmpfile[_POSIX_PATH_MAX];
    char buf[_POSIX_PATH_MAX];

    /* 检查是否登录 */
    if (session_checkloginstat(pses) < 0) {
        send_reply(pses->ctrlconn_sockfd, 530, "Loggin incorrect"); /* 530: 没有登录 */
        return;
    }

    /* 解析接收命令参数 */
    if (strcmp(pses->arg, "") == 0 || strcmp(pses->arg, "-l") == 0) { /* 缺省参数 */
        /* 获取当前工作目录 */
        if (getcwd(buf, sizeof(buf)) == NULL) {
            perror("getcwd error");
            return;
        }

        /* 将指定目录下的所有文件信息列出，并写入指定文件夹，用于数据连接传输给客户端 */
        if (pses->list_type == List_Type_Detail) {
            /* 将文件列表写到文件，便于调试查看结果， 但同时增加了磁盘IO */
            if (list_file_lsformat(buf, tmpfile) < 0) {
                /* 451: 请求动作终止，发生本地错误 */
                send_reply(pses->ctrlconn_sockfd, 451, "Requested action aborted.");
                return;
            }
        }
        else {
            if (list_file(buf, tmpfile) < 0) {
                /* 451: 请求动作终止，发生本地错误 */
                send_reply(pses->ctrlconn_sockfd, 451, "Requested action aborted.");
                return;
            }
        }
    }
    else {
        send_reply(pses->ctrlconn_sockfd, 501, "Unrecognized argument"); /* 命令参数无法识别 */
        return;
    }

    /* 检查数据连接是否打开 */
    if (pses->dataconn_sockfd < 0) {
        send_reply(pses->ctrlconn_sockfd, 425, "Can't open data connection."); /* 425: 不能打开数据连接 */
        return;
    }
    send_reply(pses->ctrlconn_sockfd, 125, "Data connection already open; Transfer starting."); /* 125: 已打开数据连接, 准备传输数据 */

    int fd;
//    strcpy(tmpfile, "list.WCMoP7");
    if ((fd = open(tmpfile, O_RDONLY)) < 0) {
        perror("action_list open error");
        /* 451: 请求动作终止，发生本地错误 */
        send_reply(pses->ctrlconn_sockfd, 451, "Requested action aborted.");
        return;
    }

    struct stat st;
    if (stat(tmpfile, &st) < 0) {
        error_at_line(0, errno, __FILE__, __LINE__, "stat error");
        /* 451: 请求动作终止，发生本地错误 */
        send_reply(pses->ctrlconn_sockfd, 451, "Requested action aborted.");
        return;
    }
    sendfile(pses->dataconn_sockfd, fd, NULL, st.st_size); /* 将包含指定目录下文件信息的文件，通过数据连接发送给客户端 */
    close(fd); /* 关闭文件 */

    send_reply(pses->ctrlconn_sockfd, 226, "Transfer complete."); /* 226: 传输完成，关闭数据连接 */
    shutdown(pses->dataconn_sockfd, SHUT_WR);  /* 关闭数据连接 */
    unlink(tmpfile);
}

/**
 * 获取系统信息
 */
static void action_syst(session_t *pses)
{
    int fd, nread;
    char buf[MAXSIZE];
    if (pses == NULL) return;

    system("uname -v > systinfo.txt");
    if ((fd = open("systinfo.txt", O_RDONLY)) < 0) {
        perror("open file error");
        return;
    }

    if ((nread = read(fd, buf, sizeof(buf))) < 0) {
        perror("read error");
        return;
    }
    buf[nread] = '\0';

    strex_trimcrlf(buf);

    send_reply(pses->ctrlconn_sockfd, 215, "UNIX Type: L8");

    close(fd);
    unlink("systinfo.txt");
}

static void action_help(session_t *pses)
{
    send_reply_multiline_first(pses->ctrlconn_sockfd, 214, "The following commands are recognized:"); /* first line */
    send_reply_multiline_middle(pses->ctrlconn_sockfd, "USER, QUIT, PORT, PASV, TYPE, LIST, STRU, RETR, STOR，NOOP.");
    send_reply_multiline_middle(pses->ctrlconn_sockfd, "SIZE, SYST, ABOR,TYPE, MODE, STRU, RETR, STOR，NOOP.");
    send_reply(pses->ctrlconn_sockfd, 214, "End of HELP."); /* last line */
}

static void action_noop(session_t *pses)
{
    send_reply(pses->ctrlconn_sockfd, 200, "OK");
}